package Command;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.StringTokenizer;
import java.util.TreeSet;
import javax.swing.undo.UndoManager;
import Common.Box2;
import Common.Box3;
import Common.Matrix3;
import Common.Matrix4;
import Common.Ray3;
import Common.Vector2f;
import Common.Vector3f;
import Common.Vector4f;
import LDraw.Support.DispatchGroup;
import LDraw.Support.GLMatrixMath;
import LDraw.Support.ILDrawDragHandler;
import LDraw.Support.LDrawDirective;
import LDraw.Support.LDrawDragHandle;
import LDraw.Support.LDrawUtilities;
import LDraw.Support.MatrixMath;
import LDraw.Support.Range;
import LDraw.Support.SelT;
import LDraw.Support.type.CacheFlagsT;
import Renderer.ILDrawCollector;
import Renderer.ILDrawRenderer;
import Renderer.LDrawRenderColorT;
public class LDrawQuadrilateral extends LDrawDrawableElement implements ILDrawDragHandler {
/**
* @uml.property name="vertex1"
* @uml.associationEnd multiplicity="(1 1)"
*/
Vector3f vertex1 = Vector3f.getZeroVector3f();
/**
* @uml.property name="vertex2"
* @uml.associationEnd multiplicity="(1 1)"
*/
Vector3f vertex2 = Vector3f.getZeroVector3f();
/**
* @uml.property name="vertex3"
* @uml.associationEnd multiplicity="(1 1)"
*/
Vector3f vertex3 = Vector3f.getZeroVector3f();
/**
* @uml.property name="vertex4"
* @uml.associationEnd multiplicity="(1 1)"
*/
Vector3f vertex4 = Vector3f.getZeroVector3f();
/**
* @uml.property name="normal"
* @uml.associationEnd multiplicity="(1 1)"
*/
Vector3f normal = Vector3f.getZeroVector3f();
/**
* @uml.property name="dragHandles"
* @uml.associationEnd multiplicity="(0 -1)"
* elementType="LDraw.Support.LDrawDragHandle"
*/
ArrayList<LDrawDragHandle> dragHandles;
// ========== initWithLines:inRange:parentGroup:
// ================================
//
// Purpose: Returns a triangle initialized from line of LDraw code beginning
// at the given range.
//
// directive should have the format:
//
// 3 colour x1 y1 z1 x2 y2 z2 x3 y3 z3
//
// ==============================================================================
public LDrawQuadrilateral initWithLines(ArrayList<String> lines,
Range range, DispatchGroup parentGroup) {
String workingLine = lines.get(range.getLocation());
String parsedField = null;
Vector3f workingVertex = Vector3f.getZeroVector3f();
LDrawColor parsedColor = null;
try {
super.initWithLines(lines, range, parentGroup);
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
// A malformed part could easily cause a string indexing error, which
// would
// raise an exception. We don't want this to happen here.
try {
// Read in the line code and advance past it.
StringTokenizer strTokenizer = new StringTokenizer(workingLine);
parsedField = strTokenizer.nextToken();
// Only attempt to create the part if this is a valid line.
if (Integer.parseInt(parsedField) == 4) {
// Read in the color code.
// (color)
parsedField = strTokenizer.nextToken();
parsedColor = LDrawUtilities.parseColorFromField(parsedField);
setLDrawColor(parsedColor);
// Read Vertex 1.
// (x1)
parsedField = strTokenizer.nextToken();
workingVertex.setX(Float.parseFloat(parsedField));
// (y1)
parsedField = strTokenizer.nextToken();
workingVertex.setY(Float.parseFloat(parsedField));
// (z1)
parsedField = strTokenizer.nextToken();
workingVertex.setZ(Float.parseFloat(parsedField));
setVertex1(workingVertex);
// Read Vertex 2.
// (x2)
parsedField = strTokenizer.nextToken();
workingVertex.setX(Float.parseFloat(parsedField));
// (y2)
parsedField = strTokenizer.nextToken();
workingVertex.setY(Float.parseFloat(parsedField));
// (z2)
parsedField = strTokenizer.nextToken();
workingVertex.setZ(Float.parseFloat(parsedField));
setVertex2(workingVertex);
// Read Vertex 3.
// (x3)
parsedField = strTokenizer.nextToken();
workingVertex.setX(Float.parseFloat(parsedField));
// (y3)
parsedField = strTokenizer.nextToken();
workingVertex.setY(Float.parseFloat(parsedField));
// (z3)
parsedField = strTokenizer.nextToken();
workingVertex.setZ(Float.parseFloat(parsedField));
setVertex3(workingVertex);
// Read Vertex 4.
// (x4)
parsedField = strTokenizer.nextToken();
workingVertex.setX(Float.parseFloat(parsedField));
// (y4)
parsedField = strTokenizer.nextToken();
workingVertex.setY(Float.parseFloat(parsedField));
// (z4)
parsedField = strTokenizer.nextToken();
workingVertex.setZ(Float.parseFloat(parsedField));
setVertex4(workingVertex);
fixBowtie();
if(parentGroup.isCCW()==false)
setToCW();
} else
throw new Exception("BricksmithParseException: "
+ "Bad line syntax");
} catch (Exception e) {
System.out.println(String.format(
"the quadrilateral primitive %s was fatally invalid",
lines.get(range.getLocation())));
System.out.println(String.format(" raised exception %s",
e.getMessage()));
}
return this;
}// end initWithLines:inRange:
// ========== drawSelf:
// ===========================================================
//
// Purpose: Draw this directive and its subdirectives by calling APIs on
// the passed in renderer, then calling drawSelf on children.
//
// Notes: Triangles use this message to get their drag handles drawn if
// needed. They do not draw their actual GL primitive because that
// has already been "collected" by some parent capable of
// accumulating a mesh.
//
// ================================================================================
public void drawSelf(ILDrawRenderer renderer) {
revalCache(CacheFlagsT.DisplayList);
if (hidden == false) {
if (dragHandles != null) {
for (LDrawDragHandle handle : dragHandles) {
handle.drawSelf(renderer);
}
}
}
}// end drawSelf:
// ========== collectSelf:
// ========================================================
//
// Purpose: Collect this is called on each directive by its parents to
// accumulate _mesh_ data into a display list for later drawing.
// The collector protocol passed in is some object capable of
// remembering the collectable data.
//
// Real GL primitives participate by passing their color and
// geometry data to the collector.
//
// ================================================================================
public void collectSelf(ILDrawCollector renderer) {
// We must mark our DL as valid - otherwise we will not invalidate our
// DL when edited, and if we don't do that, we won't pass the message
// to our parents that our DL is invalid. This passing the invalid DL up
// is what PRIMES our parent model to rebuild DLs as needed.
revalCache(CacheFlagsT.DisplayList);
if(color==null)return;
if (hidden == false) {
float v[] = { vertex1.getX(), vertex1.getY(), vertex1.getZ(),
vertex2.getX(), vertex2.getY(), vertex2.getZ(),
vertex3.getX(), vertex3.getY(), vertex3.getZ(),
vertex4.getX(), vertex4.getY(), vertex4.getZ() };
float n[] = { normal.getX(), normal.getY(), normal.getZ() };
if (color.getColorCode() == LDrawColorT.LDrawCurrentColor)
renderer.drawQuad(v, n,
LDrawRenderColorT.LDrawRenderCurrentColor.getValue());
else if (color.getColorCode() == LDrawColorT.LDrawEdgeColor)
renderer.drawQuad(v, n,
LDrawRenderColorT.LDrawRenderComplimentColor.getValue());
else {
float rgba[] = new float[4];
color.getColorRGBA(rgba);
renderer.drawQuad(v, n, rgba);
}
}
}// end collectSelf:
public void getRange( Matrix4 transform, float[] range )
{
if (hidden == false) {
Vector3f worldVertex1 = MatrixMath.V3MulPointByProjMatrix(vertex1,
transform);
Vector3f worldVertex2 = MatrixMath.V3MulPointByProjMatrix(vertex2,
transform);
Vector3f worldVertex3 = MatrixMath.V3MulPointByProjMatrix(vertex3,
transform);
Vector3f worldVertex4 = MatrixMath.V3MulPointByProjMatrix(vertex4,
transform);
compareRange( range, worldVertex1 );
compareRange( range, worldVertex2 );
compareRange( range, worldVertex3 );
compareRange( range, worldVertex4 );
if (dragHandles != null) {
for (LDrawDragHandle handle : dragHandles) {
handle.getRange(transform, range);
}
}
}
}
// ========== hitTest:transform:viewScale:boundsOnly:creditObject:hits:
// =======
//
// Purpose: Tests the directive and any of its children for intersections
// between the pickRay and the directive's drawn content.
//
// ==============================================================================
public void hitTest(Ray3 pickRay, Matrix4 transform, LDrawDirective creditObject,
HashMap<LDrawDirective, Float> hits) {
if (hidden == false) {
Vector3f worldVertex1 = MatrixMath.V3MulPointByProjMatrix(vertex1,
transform);
Vector3f worldVertex2 = MatrixMath.V3MulPointByProjMatrix(vertex2,
transform);
Vector3f worldVertex3 = MatrixMath.V3MulPointByProjMatrix(vertex3,
transform);
Vector3f worldVertex4 = MatrixMath.V3MulPointByProjMatrix(vertex4,
transform);
FloatBuffer intersectDepth = FloatBuffer.allocate(1);
boolean intersects = false;
intersects = MatrixMath.V3RayIntersectsTriangle(pickRay,
worldVertex1, worldVertex2, worldVertex3, intersectDepth,
null);
if (intersects == false) {
intersects = MatrixMath.V3RayIntersectsTriangle(pickRay,
worldVertex3, worldVertex4, worldVertex1,
intersectDepth, null);
}
if (intersects) {
LDrawUtilities.registerHitForObject(this, intersectDepth,
creditObject, hits);
}
if (dragHandles != null) {
for (LDrawDragHandle handle : dragHandles) {
handle.hitTest(pickRay, transform, null, hits);
}
}
}
}// end hitTest:transform:viewScale:boundsOnly:creditObject:hits:
// ========== boxTest:transform:boundsOnly:creditObject:hits:
// ===================
//
// Purpose: Check for intersections with screen-space geometry.
//
// ==============================================================================
public boolean boxTest(Box2 bounds, Matrix4 transform, boolean boundsOnly,
LDrawDirective creditObject, TreeSet<LDrawDirective> hits) {
if (hidden == false) {
Vector4f clipVertex1 = MatrixMath.V4MulPointByMatrix(
MatrixMath.V4FromVector3f(vertex1), transform);
Vector4f clipVertex2 = MatrixMath.V4MulPointByMatrix(
MatrixMath.V4FromVector3f(vertex2), transform);
Vector4f clipVertex3 = MatrixMath.V4MulPointByMatrix(
MatrixMath.V4FromVector3f(vertex3), transform);
Vector4f clipVertex4 = MatrixMath.V4MulPointByMatrix(
MatrixMath.V4FromVector3f(vertex4), transform);
float h_tri1[] = { clipVertex1.getX(), clipVertex1.getY(),
clipVertex1.getZ(), clipVertex1.getW(), clipVertex2.getX(),
clipVertex2.getY(), clipVertex2.getZ(), clipVertex2.getW(),
clipVertex3.getX(), clipVertex3.getY(), clipVertex3.getZ(),
clipVertex3.getW() };
float h_tri2[] = { clipVertex3.getX(), clipVertex3.getY(),
clipVertex3.getZ(), clipVertex3.getW(), clipVertex4.getX(),
clipVertex4.getY(), clipVertex4.getZ(), clipVertex4.getW(),
clipVertex1.getX(), clipVertex1.getY(), clipVertex1.getZ(),
clipVertex1.getW() };
float ndc_tris[] = new float[36];
int triCount = GLMatrixMath.clipTriangle(h_tri1, ndc_tris);
triCount += GLMatrixMath.clipTriangle(h_tri2, 0, ndc_tris,
9 * triCount);
int i;
for (i = 0; i < triCount; ++i) {
Vector2f tri[] = {
MatrixMath.V2Make(ndc_tris[i * 9 + 0],
ndc_tris[i * 9 + 1]),
MatrixMath.V2Make(ndc_tris[i * 9 + 3],
ndc_tris[i * 9 + 4]),
MatrixMath.V2Make(ndc_tris[i * 9 + 6],
ndc_tris[i * 9 + 7]) };
if (MatrixMath.V2BoxIntersectsPolygon(bounds, tri, 3)) {
LDrawUtilities.registerHitForObject(this, creditObject,
hits);
if (creditObject != null)
return true;
}
}
}
return false;
}// end boxTest:transform:boundsOnly:creditObject:hits:
// ==========
// depthTest:inBox:transform:creditObject:bestObject:bestDepth:=======
//
// Purpose: depthTest finds the closest primitive (in screen space)
// overlapping a given point, as well as its device coordinate
// depth.
//
// ==============================================================================
public void depthTest(Vector2f pt, Box2 bounds, Matrix4 transform,
LDrawDirective creditObject, ArrayList<LDrawDirective> bestObject,
FloatBuffer bestDepth) {
if (hidden == false) {
Vector4f clipVertex1 = MatrixMath.V4MulPointByMatrix(
MatrixMath.V4FromVector3f(vertex1), transform);
Vector4f clipVertex2 = MatrixMath.V4MulPointByMatrix(
MatrixMath.V4FromVector3f(vertex2), transform);
Vector4f clipVertex3 = MatrixMath.V4MulPointByMatrix(
MatrixMath.V4FromVector3f(vertex3), transform);
Vector4f clipVertex4 = MatrixMath.V4MulPointByMatrix(
MatrixMath.V4FromVector3f(vertex4), transform);
Vector3f probe = new Vector3f(pt.getX(), pt.getY(),
bestDepth.get(0));
float h_tri1[] = { clipVertex1.getX(), clipVertex1.getY(),
clipVertex1.getZ(), clipVertex1.getW(), clipVertex2.getX(),
clipVertex2.getY(), clipVertex2.getZ(), clipVertex2.getW(),
clipVertex3.getX(), clipVertex3.getY(), clipVertex3.getZ(),
clipVertex3.getW() };
float h_tri2[] = { clipVertex3.getX(), clipVertex3.getY(),
clipVertex3.getZ(), clipVertex3.getW(), clipVertex4.getX(),
clipVertex4.getY(), clipVertex4.getZ(), clipVertex4.getW(),
clipVertex1.getX(), clipVertex1.getY(), clipVertex1.getZ(),
clipVertex1.getW() };
float ndc_tris[] = new float[36];
int triCount = GLMatrixMath.clipTriangle(h_tri1, ndc_tris);
triCount += GLMatrixMath.clipTriangle(h_tri2, 0, ndc_tris,
9 * triCount);
int i;
for (i = 0; i < triCount; ++i) {
Vector3f ndcVertex1 = MatrixMath.V3Make(ndc_tris[i * 9 + 0],
ndc_tris[i * 9 + 1], ndc_tris[i * 9 + 2]);
Vector3f ndcVertex2 = MatrixMath.V3Make(ndc_tris[i * 9 + 3],
ndc_tris[i * 9 + 4], ndc_tris[i * 9 + 5]);
Vector3f ndcVertex3 = MatrixMath.V3Make(ndc_tris[i * 9 + 6],
ndc_tris[i * 9 + 7], ndc_tris[i * 9 + 8]);
if (MatrixMath.DepthOnTriangle(ndcVertex1, ndcVertex2,
ndcVertex3, probe)) {
if (probe.getZ() <= bestDepth.get(0)) {
bestDepth.put(0, probe.getZ());
bestObject
.add((LDrawDirective) (creditObject != null ? creditObject
: this));
}
}
}
if (dragHandles != null) {
for (LDrawDragHandle handle : dragHandles) {
handle.depthTest(pt, bounds, transform, creditObject,
bestObject, bestDepth);
}
}
}
}// end depthTest:inBox:transform:creditObject:bestObject:bestDepth:
// ========== write
// =============================================================
//
// Purpose: Returns a line that can be written out to a file.
// Line format:
// 3 colour x1 y1 z1 x2 y2 z2 x3 y3 z3
//
// ==============================================================================
public String write() {
return String.format("4 %s %s %s %s %s %s %s %s %s %s %s %s %s",
LDrawUtilities.outputStringForColor(color),
LDrawUtilities.outputStringForFloat(vertex1.getX()),
LDrawUtilities.outputStringForFloat(vertex1.getY()),
LDrawUtilities.outputStringForFloat(vertex1.getZ()),
LDrawUtilities.outputStringForFloat(vertex2.getX()),
LDrawUtilities.outputStringForFloat(vertex2.getY()),
LDrawUtilities.outputStringForFloat(vertex2.getZ()),
LDrawUtilities.outputStringForFloat(vertex3.getX()),
LDrawUtilities.outputStringForFloat(vertex3.getY()),
LDrawUtilities.outputStringForFloat(vertex3.getZ()),
LDrawUtilities.outputStringForFloat(vertex4.getX()),
LDrawUtilities.outputStringForFloat(vertex4.getY()),
LDrawUtilities.outputStringForFloat(vertex4.getZ())
);
}// end write
// ========== browsingDescription
// ===============================================
//
// Purpose: Returns a representation of the directive as a short string
// which can be presented to the user.
//
// ==============================================================================
public String browsingDescription() {
return "Quadrilateral";
}// end browsingDescription
// ========== iconName
// ==========================================================
//
// Purpose: Returns the name of image file used to display this kind of
// object, or null if there is no icon.
//
// ==============================================================================
public String iconName() {
return "Quadrilateral";
}// end iconName
// ========== inspectorClassName
// ================================================
//
// Purpose: Returns the name of the class used to inspect this one.
//
// ==============================================================================
public String inspectorClassName() {
return "InspectionQuadrilateral";
}// end inspectorClassName
// #pragma mark -
// #pragma mark ACCESSORS
// #pragma mark -
// ========== boundingBox3
// ======================================================
//
// Purpose: Returns the minimum and maximum points of the box which
// perfectly contains this object.
//
// ==============================================================================
public Box3 boundingBox3() {
// Raw directive doesn't cache - we just compute our bbox on the fly.
// But
// keep our parents "in sync".
revalCache(CacheFlagsT.CacheFlagBounds);
if (hidden == true)
return Box3.getInvalidBox();
Box3 bounds;
// Compare first two points.
bounds = MatrixMath.V3BoundsFromPoints(vertex1, vertex2);
// Now toss the third vertex into the mix.
bounds = MatrixMath.V3UnionBoxAndPoint(bounds, vertex3);
bounds = MatrixMath.V3UnionBoxAndPoint(bounds, vertex4);
return bounds;
}// end boundingBox3
// ========== position
// ==========================================================
//
// Purpose: Returns some position for the element. This is used by
// drag-and-drop. This is not necessarily human-usable information.
//
// ==============================================================================
public Vector3f position() {
return vertex1;
}// end position
// ========== vertex1
// ===========================================================
// ==============================================================================
public Vector3f vertex1() {
return vertex1;
}// end vertex1
// ========== vertex2
// ===========================================================
// ==============================================================================
public Vector3f vertex2() {
return vertex2;
}// end vertex2
// ========== vertex3
// ===========================================================
// ==============================================================================
public Vector3f vertex3() {
return vertex3;
}// end vertex3
// ========== vertex4
// ===========================================================
// ==============================================================================
public Vector3f vertex4() {
return vertex4;
}// end vertex3
// #pragma mark -
// ========== setSelected:
// ======================================================
//
// Purpose: Somebody make this a protocol method.
//
// ==============================================================================
public void setSelected( boolean flag) {
super.setSelected(flag);
if (flag == true) {
LDrawDragHandle handle1 = new LDrawDragHandle();
handle1.initWithTag(1, vertex1);
LDrawDragHandle handle2 = new LDrawDragHandle();
handle1.initWithTag(2, vertex2);
LDrawDragHandle handle3 = new LDrawDragHandle();
handle1.initWithTag(3, vertex3);
LDrawDragHandle handle4 = new LDrawDragHandle();
handle1.initWithTag(4, vertex4);
handle1.setTarget(this);
handle2.setTarget(this);
handle3.setTarget(this);
handle4.setTarget(this);
handle1.setAction(SelT.DragHandleChanged);
handle2.setAction(SelT.DragHandleChanged);
handle3.setAction(SelT.DragHandleChanged);
handle4.setAction(SelT.DragHandleChanged);
dragHandles = new ArrayList<LDrawDragHandle>();
dragHandles.add(handle1);
dragHandles.add(handle2);
dragHandles.add(handle3);
dragHandles.add(handle4);
} else {
dragHandles = null;
}
}// end setSelected:
// ========== setVertex1:
// =======================================================
//
// Purpose: Sets the triangle's first vertex.
//
// ==============================================================================
/**
* @param newVertex
* @uml.property name="vertex1"
*/
public void setVertex1(Vector3f newVertex) {
vertex1.set(newVertex);
recomputeNormal();
invalCache(CacheFlagsT.CacheFlagBounds);
invalCache(CacheFlagsT.DisplayList);
if (dragHandles != null) {
dragHandles.get(0).setPosition(newVertex, false);
}
if (enclosingDirective() != null)
enclosingDirective().setVertexesNeedRebuilding();
}// end setVertex1:
// ========== setVertex2:
// =======================================================
//
// Purpose: Sets the triangle's second vertex.
//
// ==============================================================================
/**
* @param newVertex
* @uml.property name="vertex2"
*/
public void setVertex2(Vector3f newVertex) {
vertex2.set(newVertex);
recomputeNormal();
invalCache(CacheFlagsT.CacheFlagBounds);
invalCache(CacheFlagsT.DisplayList);
if (dragHandles != null) {
dragHandles.get(1).setPosition(newVertex, false);
}
if (enclosingDirective() != null)
enclosingDirective().setVertexesNeedRebuilding();
}// end setVertex2:
// ========== setVertex3:
// =======================================================
//
// Purpose: Sets the triangle's last vertex.
//
// ==============================================================================
/**
* @param newVertex
* @uml.property name="vertex3"
*/
public void setVertex3(Vector3f newVertex) {
vertex3.set(newVertex);
recomputeNormal();
invalCache(CacheFlagsT.CacheFlagBounds);
invalCache(CacheFlagsT.DisplayList);
if (dragHandles != null) {
dragHandles.get(2).setPosition(newVertex, false);
}
if (enclosingDirective() != null)
enclosingDirective().setVertexesNeedRebuilding();
}// end setVertex3:
// ========== setVertex4:
// =======================================================
//
// Purpose: Sets the triangle's last vertex.
//
// ==============================================================================
/**
* @param newVertex
* @uml.property name="vertex4"
*/
public void setVertex4(Vector3f newVertex) {
vertex4.set(newVertex);
recomputeNormal();
invalCache(CacheFlagsT.CacheFlagBounds);
invalCache(CacheFlagsT.DisplayList);
if (dragHandles != null) {
dragHandles.get(3).setPosition(newVertex, false);
}
if (enclosingDirective() != null)
enclosingDirective().setVertexesNeedRebuilding();
}// end setVertex4:
//
// #pragma mark -
// #pragma mark ACTIONS
// #pragma mark -
// ========== dragHandleChanged:
// ================================================
//
// Purpose: One of the drag handles on our vertexes has changed.
//
// ==============================================================================
public void dragHandleChanged(LDrawDragHandle sender) {
LDrawDragHandle handle = (LDrawDragHandle) sender;
Vector3f newPosition = handle.position();
int vertexNumber = handle.tag();
switch (vertexNumber) {
case 1:
setVertex1(newPosition);
break;
case 2:
setVertex2(newPosition);
break;
case 3:
setVertex3(newPosition);
break;
case 4:
setVertex4(newPosition);
break;
}
}// end dragHandleChanged:
// ========== moveBy:
// ===========================================================
//
// Purpose: Moves the receiver in the specified direction.
//
// ==============================================================================
public void moveBy(Vector3f moveVector) {
Vector3f newVertex1 = MatrixMath.V3Add(vertex1, moveVector);
Vector3f newVertex2 = MatrixMath.V3Add(vertex2, moveVector);
Vector3f newVertex3 = MatrixMath.V3Add(vertex3, moveVector);
Vector3f newVertex4 = MatrixMath.V3Add(vertex4, moveVector);
setVertex1(newVertex1);
setVertex2(newVertex2);
setVertex3(newVertex3);
setVertex4(newVertex4);
}// end moveBy:
// #pragma mark -
// #pragma mark UTILITIES
// #pragma mark -
// ========== fixBowtie
// =========================================================
//
// Purpose: Four points in any order define a quadrilateral, but if you want
// to draw one in OpenGL, you need to be able to trace around the
// edges in order. If two vertices are out of order, you wind up
// with a "bowtie" shape, which needs to be corrected back into a
// quadrilateral.
//
// 4 3 3 4 4 2
// +------+ +------+ + +
// | | \ / |\ /|
// | | \ / | \ / |
// | | \/ | \/ |
// | | /\ | /\ |
// | | / \ | / \ |
// | | / \ |/ \|
// +------+ +------+ + +
// 1 2 1 2 1 3
//
// correct case 1 case 2
// switch 3 & 4 switch 2 & 3
//
// ==============================================================================
public void fixBowtie() {
// If correct, the crosses of these three pairs should all point up.
Vector3f vector1_2, vector1_4; // 1 to 2, 1 to 4
Vector3f vector3_4, vector3_2;
Vector3f vector4_1, vector4_3;
Vector3f cross1, cross3, cross4;
vector1_2 = MatrixMath.V3Sub(vertex2, vertex1);
vector1_4 = MatrixMath.V3Sub(vertex4, vertex1);
vector3_4 = MatrixMath.V3Sub(vertex4, vertex3);
vector3_2 = MatrixMath.V3Sub(vertex2, vertex3);
vector4_1 = MatrixMath.V3Sub(vertex1, vertex4);
vector4_3 = MatrixMath.V3Sub(vertex3, vertex4);
cross1 = MatrixMath.V3Cross(vector1_2, vector1_4);
cross3 = MatrixMath.V3Cross(vector3_4, vector3_2);
cross4 = MatrixMath.V3Cross(vector4_1, vector4_3);
// When crosses point different directions, we have a bowtie. To test
// this,
// recall that cos x = (u ??v) / (||u|| ||v||)
// cos(180) = -1 and cos(0) = 1. So if u?�v is negative, we have opposing
// vectors (since the denominator is always positive, we can ignore it).
// If 1 & 4 point opposite directions, we have a case 1 bowtie
if (MatrixMath.V3Dot(cross1, cross4) < 0) {
// vectors point in opposite directions
Vector3f swapPoint = vertex3;
vertex3 = vertex4;
vertex4 = swapPoint;
}
// If 3 & 4 point opposite directions, we have a case 2 bowtie
else if (MatrixMath.V3Dot(cross3, cross4) < 0) {
Vector3f swapPoint = vertex2;
vertex2 = vertex3;
vertex3 = swapPoint;
}
}// end fixBowtie
// ========== flattenIntoLines:triangles:quadrilaterals:other:currentColor:
// =====
//
// Purpose: Appends the directive into the appropriate container.
//
// ==============================================================================
public void flattenIntoLines(ArrayList<LDrawLine> lines,
ArrayList<LDrawTriangle> triangles,
ArrayList<LDrawQuadrilateral> quadrilaterals,
ArrayList<LDrawDirective> everythingElse, LDrawColor parentColor,
Matrix4 transform, Matrix3 normalTransform, boolean recursive) {
super.flattenIntoLines(lines, triangles, quadrilaterals,
everythingElse, parentColor, transform, normalTransform,
recursive);
vertex1 = MatrixMath.V3MulPointByProjMatrix(vertex1, transform);
vertex2 = MatrixMath.V3MulPointByProjMatrix(vertex2, transform);
vertex3 = MatrixMath.V3MulPointByProjMatrix(vertex3, transform);
vertex4 = MatrixMath.V3MulPointByProjMatrix(vertex4, transform);
normal = MatrixMath.V3MulPointByMatrix(normal, normalTransform);
quadrilaterals.add(this);
}// end flattenIntoLines:triangles:quadrilaterals:other:currentColor:
// ========== recomputeNormal
// ===================================================
//
// Purpose: Finds the normal vector for this surface.
//
// ==============================================================================
public void recomputeNormal() {
Vector3f vector1, vector2;
vector1 = MatrixMath.V3Sub(vertex2, vertex1);
vector2 = MatrixMath.V3Sub(vertex4, vertex1);
normal = MatrixMath.V3Cross(vector1, vector2);
}// end recomputeNormal
// ========== registerUndoActions
// ===============================================
//
// Purpose: Registers the undo actions that are unique to this subclass,
// not to any superclass.
//
// ==============================================================================
public void registerUndoActions(UndoManager undoManager) {
super.registerUndoActions(undoManager);
// todo
// [[undoManager prepareWithInvocationTarget:self] setVertex3:vertex3]);
// [[undoManager prepareWithInvocationTarget:self] setVertex2:vertex2]);
// [[undoManager prepareWithInvocationTarget:self] setVertex1:vertex1]);
// [undoManager setActionName:NSLocalizedString(@"UndoAttributesLine",
// null));
}// end registerUndoActions:
public void setToCW() {
Vector3f tempVector = vertex4;
vertex4 = vertex1;
vertex1 = tempVector;
tempVector = vertex3;
vertex3 = vertex2;
vertex2 = tempVector;
recomputeNormal();
}
}